/*
 * Copyright 2024 ywcai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

`include "defines.v"
`timescale 1ns/1ps

module icache(
	input	wire						clk,
	input	wire						rst_n,

	input	wire						req_i,
	input	wire[`MemAddrBus]			addr_i,
	input	wire[`CacheBus]				data_l2_i,
	input	wire						req_done_i,

	output	reg[`MemAddrBus]			addr_o,
	output	reg							req_grant_o,
	output	reg[`InstDataBus]			inst_o,
	output	reg							inst_ready_o,
	output	reg							missing_req_o,
	output	wire[7:0]					bytes_req_o,
	output	wire[7:0]					bytes_offset_o,
	output	wire						i_load_stall_req_o
	);

	localparam IDLE		= 4'b0001;
	localparam MATCH	= 4'b0010;
	localparam LOAD		= 4'b0100;
	localparam OUTPUT	= 4'b1000;

	//--------------------------------------------------------------------------
	reg[`MemAddrBus]	match_addr;
	reg[4:0]			cache_set;
	reg[31:0]			lru;	// the bank can be replaced in next time
	reg[3:0]			cur_state, next_state;
	reg					cur_way;
	reg					stall_req;

	//	{valid, dirty, tag, data}
	reg					en_1;
	reg					we_1;
	wire[542:0]			rdata_set_1;
	reg[542:0]			wdata_set_1;

	gen_cache bank_1(
		.clk(clk),
		.rst_n(rst_n),
		.en_i(en_1),
		.addr_i(cache_set),
		.we_i(we_1),
		.wdata_i(wdata_set_1),
		.rdata_o(rdata_set_1)
	);

	reg					en_2;
	reg					we_2;
	wire[542:0]			rdata_set_2;
	reg[542:0]			wdata_set_2;

	gen_cache bank_2(
		.clk(clk),
		.rst_n(rst_n),
		.en_i(en_2),
		.addr_i(cache_set),
		.we_i(we_2),
		.wdata_i(wdata_set_2),
		.rdata_o(rdata_set_2)
	);

	//--------------------------------------------------------------------------
	assign i_load_stall_req_o = stall_req;

	wire addr_valid = (addr_i >= `ROM_BASE) && (addr_i < `UART_BASE);

	wire[`MemAddrBus] aligned_start = (match_addr >> 6) << 6;
	wire[`MemAddrBus] addr_start = (aligned_start < `ROM_BASE)
		? `ROM_BASE : aligned_start;

	wire[`MemAddrBus] aligned_end = (((match_addr + 64) >> 6) << 6) - 1;
	wire[`MemAddrBus] addr_end = (aligned_end >= `UART_BASE)
		? (`UART_BASE - 1) : aligned_end;

	assign bytes_req_o = addr_end - addr_start + 1;
	assign bytes_offset_o = (aligned_start < `ROM_BASE)
		? {2'b00, addr_start[5:0]} : 8'h0;

	wire[4:0]	set_idx = match_addr[10:6];
	wire[28:0]	tag_addr = match_addr[39:11];
	wire[11:0]	offset = {8'h0, match_addr[5:2]};

	wire[`InstDataBus] data_way1 =
		({32{offset == 4'b0000}} & rdata_set_1[31:0])
		| ({32{offset == 4'b0001}} & rdata_set_1[63:32])
		| ({32{offset == 4'b0010}} & rdata_set_1[95:64])
		| ({32{offset == 4'b0011}} & rdata_set_1[127:96])
		| ({32{offset == 4'b0100}} & rdata_set_1[159:128])
		| ({32{offset == 4'b0101}} & rdata_set_1[191:160])
		| ({32{offset == 4'b0110}} & rdata_set_1[223:192])
		| ({32{offset == 4'b0111}} & rdata_set_1[255:224])
		| ({32{offset == 4'b1000}} & rdata_set_1[287:256])
		| ({32{offset == 4'b1001}} & rdata_set_1[319:288])
		| ({32{offset == 4'b1010}} & rdata_set_1[351:320])
		| ({32{offset == 4'b1011}} & rdata_set_1[383:352])
		| ({32{offset == 4'b1100}} & rdata_set_1[415:384])
		| ({32{offset == 4'b1101}} & rdata_set_1[447:416])
		| ({32{offset == 4'b1110}} & rdata_set_1[479:448])
		| ({32{offset == 4'b1111}} & rdata_set_1[511:480]);

	wire[`InstDataBus] data_way2 =
		({32{offset == 4'b0000}} & rdata_set_2[31:0])
		| ({32{offset == 4'b0001}} & rdata_set_2[63:32])
		| ({32{offset == 4'b0010}} & rdata_set_2[95:64])
		| ({32{offset == 4'b0011}} & rdata_set_2[127:96])
		| ({32{offset == 4'b0100}} & rdata_set_2[159:128])
		| ({32{offset == 4'b0101}} & rdata_set_2[191:160])
		| ({32{offset == 4'b0110}} & rdata_set_2[223:192])
		| ({32{offset == 4'b0111}} & rdata_set_2[255:224])
		| ({32{offset == 4'b1000}} & rdata_set_2[287:256])
		| ({32{offset == 4'b1001}} & rdata_set_2[319:288])
		| ({32{offset == 4'b1010}} & rdata_set_2[351:320])
		| ({32{offset == 4'b1011}} & rdata_set_2[383:352])
		| ({32{offset == 4'b1100}} & rdata_set_2[415:384])
		| ({32{offset == 4'b1101}} & rdata_set_2[447:416])
		| ({32{offset == 4'b1110}} & rdata_set_2[479:448])
		| ({32{offset == 4'b1111}} & rdata_set_2[511:480]);

	wire[28:0]	tag_way1 = rdata_set_1[540:512];
	wire[28:0]	tag_way2 = rdata_set_2[540:512];
	wire		valid_way1 = rdata_set_1[542];
	wire		valid_way2 = rdata_set_2[542];

	wire hit_way1 = (tag_addr == tag_way1) && valid_way1;
	wire hit_way2 = (tag_addr == tag_way2) && valid_way2;

	always @(posedge clk) begin
	    if (rst_n == `RESET_ENABLE) begin
			cur_state <= IDLE;
		end else begin
			cur_state <= next_state;
		end
	end

	always @(posedge clk) begin
	    if (rst_n == `RESET_ENABLE) begin
			match_addr <= `ZERO_ADDR;
		end else begin
			if (req_i && addr_valid && req_grant_o) begin
				match_addr <= addr_i;
			end
		end
	end

	always @(*) begin
		if (rst_n == `RESET_ENABLE) begin
			next_state = IDLE;
			addr_o = `ZERO_ADDR;
			inst_o = `INST_NOP;
			req_grant_o = `DISABLE;
			inst_ready_o = `DISABLE;
			missing_req_o = `DISABLE;
			cur_way = `DISABLE;
			lru = 32'h0;
			stall_req = `DISABLE;
			en_1 = `DISABLE;
			en_2 = `DISABLE;
			we_1 = `DISABLE;
			we_2 = `DISABLE;
		end else begin
			case (cur_state)
				IDLE: begin
					if (!req_i || !addr_valid) begin
						req_grant_o = `DISABLE;
						inst_ready_o = `DISABLE;
						next_state = IDLE;
					end else begin
						req_grant_o = `ENABLE;
						inst_ready_o = `DISABLE;
						en_1 = `ENABLE;
						we_1 = `DISABLE;
						en_2 = `ENABLE;
						we_2 = `DISABLE;
						cache_set = addr_i[10:6];
						next_state = MATCH;
					end
				end

				MATCH: begin
					en_1 = `DISABLE;
					we_1 = `DISABLE;
					en_2 = `DISABLE;
					we_2 = `DISABLE;
					if (!hit_way1 && !hit_way2) begin
						addr_o = addr_start;
						missing_req_o = `ENABLE;
						stall_req = `ENABLE;
						req_grant_o = `DISABLE;
						inst_ready_o = `DISABLE;
						next_state = LOAD;
					end else begin
						if (hit_way1) begin
							inst_o = data_way1;
							lru[set_idx] = `ENABLE;
						end else begin
							inst_o = data_way2;
							lru[set_idx] = `DISABLE;
						end
						inst_ready_o = `ENABLE;

						if (req_i && addr_valid) begin
							req_grant_o = `ENABLE;
							en_1 = `ENABLE;
							en_2 = `ENABLE;
							cache_set = addr_i[10:6];
							next_state = MATCH;
						end else begin
							req_grant_o = `DISABLE;
							next_state = IDLE;
						end
					end
				end

				LOAD: begin
					if (req_done_i) begin
						cache_set = match_addr[10:6];
						if (!lru[set_idx]) begin
							en_1 = `ENABLE;
							we_1 = `ENABLE;
							en_2 = `DISABLE;
							we_2 = `DISABLE;
							wdata_set_1 = {2'b10, tag_addr, data_l2_i};
							cur_way = `DISABLE;
						end else begin
							en_1 = `DISABLE;
							we_1 = `DISABLE;
							en_2 = `ENABLE;
							we_2 = `ENABLE;
							wdata_set_2 = {2'b10, tag_addr, data_l2_i};
							cur_way = `ENABLE;
						end
						inst_o = data_l2_i[offset<<5 +: 32];
						next_state = OUTPUT;
					end else begin
						missing_req_o = `DISABLE;
						next_state = LOAD;
					end
				end

				OUTPUT: begin
					//	next dumpage cache line
					if (!cur_way) begin
						lru[set_idx] = `ENABLE;
					end else begin
						lru[set_idx] = `DISABLE;
					end
					en_1 = `DISABLE;
					en_2 = `DISABLE;
					we_1 = `DISABLE;
					we_2 = `DISABLE;
					inst_ready_o = `ENABLE;
					stall_req = `DISABLE;
					next_state = IDLE;
				end

				default: begin
					next_state = IDLE;
				end
			endcase
		end
	end

endmodule
